﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace LibSnesDotNet.Threading
{
	class WorkerThread : IDisposable
	{
		Thread             _thread;
		AutoResetEvent     _taskHandle;

		WaitableBool       _isBusy;
		volatile bool      _isDisposing;
		volatile Delegate  _action;
		volatile object[]  _arguments;
		volatile Exception _exception;
		volatile object    _result;

		public bool IsBusy
		{
			get
			{
				if (_exception != null) { throw new Exception("Exception thrown during work.", _exception); }

				return new ReadOnlyWaitableBool(_isBusy);
			}
		}

		public bool IsDisposing { get { return _isDisposing; } }

		public object Result
		{
			get
			{
				if (_exception != null) { throw new Exception("Exception thrown during work.", _exception); }
				if (_isBusy)            { throw new InvalidOperationException("Work has not completed."); }

				return _result;
			}
		}

		public WorkerThread()
		{
			_isDisposing = false;

			_taskHandle = new AutoResetEvent(false);

			_isBusy = new WaitableBool(false);
			_thread = new Thread(WorkPump);

			_thread.Start();
			WaitFor();
		}

		public void DoWork(Action action)
		{
			DoWork((Delegate)action);
		}

		public void DoWork(Delegate action, params object[] arguments)
		{
			_isBusy.Acquire(true);

			_action    = action;
			_arguments = arguments;
			_exception = null;
			_result    = null;

			_taskHandle.Set();
		}

		public void WaitFor()
		{
			_isBusy.WaitWhile();
		}

		public T WaitForResult<T>()
		{
			_isBusy.WaitWhile();

			return (T)Result;
		}

		void WorkPump()
		{
			while (!_isDisposing)
			{
				_taskHandle.WaitOne();

				if (!_isDisposing)
				{
					try { _result = _action.DynamicInvoke(_arguments); }
					catch (Exception e) { _exception = e; }
				}

				_isBusy.Value = false;
			}
		}

		public void Dispose()
		{
			_isDisposing = true;

			WaitFor();
			_taskHandle.Set();
			_isBusy.WaitWhile();
		}
	}
}
